/*****************************************************************************
 * main.c : program entry
 *****************************************************************************
 * Copyright (C) 2011 PELinker
 *
 * Authors: Iceberg <iceberg58744@163.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#include "parse.h"
#include "process.h"
#include "load.h"
#include "memory.h"
#include "util.h"

#define ALIGN_MASK 0x00f00000

#define CODE_BASE_ADDRESS 0x1000

#define CODE_PADDING 0xcc

static Command cmd;

static Link_Option opt =
{
	0,			/* int bPrintHelpInfo; */
	0,			/* int bSetMapFileName; */
	{0, 0},		/* String MapFileName; */
	0,			/* int bSetOutputFileName; */
	{0, 0},		/* String OutputFileName; */
	0,			/* int bSetOutputFileType; */
	0,			/* DWORD OutputFileType; 0 for exe, 1 for dll */
	0,			/* int bSetEntryPoint; */
	{0, 0},		/* String EntryPoint; */
	0,			/* int bSetImageBase; */
	0x00400000,	/* DWORD ImageBase; */
	0,			/* int bSetSectionAlign; */
	0x00001000,	/* DWORD SectionAlign; */
	0,			/* int bSetFileAlign; */
	0x00000200,	/* DWORD FileAlign; */
	0,			/* int bSetSubsystem; */
	2,			/* DWORD Subsystem; 1 for native, 2 for windows, 3 for console */
	0,			/* int bSetLinkerVersion; */
	{1, 0},		/* WORD LinkerVersion[2]; */
	0,			/* int bSetOSVersion; */
	{4, 0},		/* WORD OSVersion[2]; */
	0,			/* int bSetImageVersion; */
	{1, 0},		/* WORD ImageVersion[2]; */
	0,			/* int bSetSubSysVersion; */
	{4, 0},		/* WORD SubSysVersion[2]; */
	0,			/* int bSetStackSize; */
	0x00100000,	/* DWORD StackSize; */
	0,			/* int bSetStackCommitSize; */
	0x00001000,	/* DWORD StackCommitSize; */
	0,			/* int bSetHeapSize; */
	0x00100000,	/* DWORD HeapSize; */
	0,			/* int bSetHeapCommitSize; */
	0x00001000	/* DWORD HeapCommitSize; */
};

static Link_Data data;

static IMAGE_PE_HEADER PE_hdr =
{
	{
		/* IMAGE_DOS_HEADER */
		0x5A4D,							/* WORD e_magic;         Magic number */
		0x0090,							/* WORD e_cblp;          Bytes on last page of file */
		0x0003,							/* WORD e_cp;            Pages in file */
		0x0000,							/* WORD e_crlc;          Relocations */
		0x0004,							/* WORD e_cparhdr;       Size of header in paragraphs */
		0x0000,							/* WORD e_minalloc;      Minimum extra paragraphs needed */
		0xFFFF,							/* WORD e_maxalloc;      Maximum extra paragraphs needed */
		0x0000,							/* WORD e_ss;            Initial (relative) SS value */
		0x00B8,							/* WORD e_sp;            Initial SP value */
		0x0000,							/* WORD e_csum;          Checksum */
		0x0000,							/* WORD e_ip;            Initial IP value */
		0x0000,							/* WORD e_cs;            Initial (relative) CS value */
		0x0040,							/* WORD e_lfarlc;        File address of relocation table */
		0x0000,							/* WORD e_ovno;          Overlay number */
		{0, 0, 0, 0},					/* WORD e_res[4];        Reserved words */
		0x0000,							/* WORD e_oemid;         OEM identifier (for e_oeminfo) */
		0x0000,							/* WORD e_oeminfo;       OEM information; e_oemid specific */
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},	/* WORD e_res2[10];      Reserved words */
		0x00000080						/* DWORD e_lfanew;       File address of new exe header */
	},
	{
		/* BYTE DOS_stub[0x40] */
		/* 14 code bytes + "This program cannot be run in DOS mode.\r\r\n$" + 6 * 0x00 */
		0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68,
		0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f,
		0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20,
		0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	},
	0x00004550,							/* DWORD NT_signature = IMAGE_NT_SIGNATURE */
	{
		/* IMAGE_FILE_HEADER */
		0x014C,							/* WORD Machine; */
		0x0000,							/* WORD NumberOfSections; */
		0x00000000,						/* DWORD TimeDateStamp; */
		0x00000000,						/* DWORD PointerToSymbolTable; */
		0x00000000,						/* DWORD NumberOfSymbols; */
		0x00E0,							/* WORD SizeOfOptionalHeader; */
		0x010F							/* WORD Characteristics; */
	},
	{
		/* IMAGE_OPTIONAL_HEADER */
		/* Standard fields. */
		0x010B,							/* WORD Magic; */
		0x01,							/* BYTE MajorLinkerVersion; */
		0x00,							/* BYTE MinorLinkerVersion; */
		0x00000000,						/* DWORD SizeOfCode; */
		0x00000000,						/* DWORD SizeOfInitializedData; */
		0x00000000,						/* DWORD SizeOfUninitializedData; */
		0x00000000,						/* DWORD AddressOfEntryPoint; */
		0x00000000,						/* DWORD BaseOfCode; */
		0x00000000,						/* DWORD BaseOfData; */

		/* NT additional fields. */
		0x00400000,						/* DWORD ImageBase; */
		0x00001000,						/* DWORD SectionAlignment; */
		0x00000200,						/* DWORD FileAlignment; */
		0x0004,							/* WORD MajorOperatingSystemVersion; */
		0x0000,							/* WORD MinorOperatingSystemVersion; */
		0x0000,							/* WORD MajorImageVersion; */
		0x0000,							/* WORD MinorImageVersion; */
		0x0004,							/* WORD MajorSubsystemVersion; */
		0x0000,							/* WORD MinorSubsystemVersion; */
		0x00000000,						/* DWORD Win32VersionValue; */
		0x00000000,						/* DWORD SizeOfImage; */
		0x00000200,						/* DWORD SizeOfHeaders; */
		0x00000000,						/* DWORD CheckSum; */
		0x0002,							/* WORD Subsystem; */
		0x0000,							/* WORD DllCharacteristics; */
		0x00100000,						/* DWORD SizeOfStackReserve; */
		0x00001000,						/* DWORD SizeOfStackCommit; */
		0x00100000,						/* DWORD SizeOfHeapReserve; */
		0x00001000,						/* DWORD SizeOfHeapCommit; */
		0x00000000,						/* DWORD LoaderFlags; */
		0x00000010,						/* DWORD NumberOfRvaAndSizes; */
		{
			/* IMAGE_DATA_DIRECTORY DataDirectory[16]; */
			{0, 0},						/* Export Directory */
			{0, 0},						/* Import Directory */
			{0, 0},						/* Resource Directory */
			{0, 0},						/* Exception Directory */
			{0, 0},						/* Certificates Directory */
			{0, 0},						/* Base Relocation Directory */
			{0, 0},						/* Debug Directory */
			{0, 0},						/* Architecture Directory */
			{0, 0},						/* Global Pointer Directory */
			{0, 0},						/* Thread Storage Directory */
			{0, 0},						/* Load Configuration Directory */
			{0, 0},						/* Bound Import Directory */
			{0, 0},						/* Import Address Table Directory */
			{0, 0},						/* Delay Import Directory */
			{0, 0},						/* COM Descriptor Directory */
			{0, 0}						/* Reserved Directory */
		}
	}
};

static const char Help[] =
	"Usage: LINKER [options] [file [file [...]]]\n"
	"\n"
	"    Each file may be an object file, a library, or a Win32 resource file.\n"
	"    Modules are only loaded from library files if they are required to match\n"
	"    an external reference.\n"
	"    Options and files may be listed in any order, all mixed together.\n"
	"\nThe following options are permitted:\n"
	"    -h      Display this help list\n"
	"    -H      Display this help list\n"
	"    -?      Display this help list\n"
	"    -L:dir  Add directory dir to search list\n"
	"    -output:name  Choose output file name\n"
	"    -format:XXX   Choose output format XXX\n"
	"        Available options are:\n"
	"            EXE  - Win32 PE Executable\n"
	"            DLL  - Win32 PE Dynamic-link library\n"
	"    -entry:name   Use public symbol name as the entry point\n"
	"\nOptions for PE files:\n"
	"    -base:hex         Set base address of image\n"
	"    -memalign:hex     Set section alignment in memory, must be power of 2\n"
	"    -filealign:hex    Set section alignment in file, must be power of 2\n"
	"    -subsystem:XXX    Set subsystem XXX used\n"
	"        Available options are:\n"
	"            native    Set native mode\n"
	"            windows   Set windowing mode\n"
	"            console   Set character mode\n"
	"    -linkerver:x.y    Set linker version x.y\n"
	"    -osver:x.y        Set OS version x.y\n"
	"    -imagever:x.y     Set image version x.y\n"
	"    -subsysver:x.y    Set subsystem version x.y\n"
	"    -stacksize:hex    Set stack size to hex\n"
	"    -stackcommitsize:hex Set stack commit size to hex\n"
	"    -heapsize:hex     Set heap size to hex\n"
	"    -heapcommitsize:hex Set heap commit size to hex\n"
	"\nAll the options can appear only once except -L.\n";

static char PrintableDWORD[] = "12345678\n";

static int sstrcmp(const BYTE * const string1, const BYTE * const string2)
{
	DWORD i = 0;

	while (string1[i] == string2[i])
	{
		if (!string1[i])
		{
			return 0;
		}

		++i;
	}

	return (string1[i] > string2[i] ? 1 : -1);
}

static int sstrcpy(BYTE * const dest, const BYTE * const src)
{
	int i = 0;

	while (src[i])
	{
		dest[i] = src[i];

		++i;
	}

	dest[i] = src[i];

	return i;
}

static int HandleOptions(const Link_Option * const link_opt)
{
	if (link_opt->bPrintHelpInfo)
	{
		PrintInfoAux(Help);

		return 1;
	}
	if (!link_opt->bSetEntryPoint)
	{
		PrintInfoAux("Entry point should be set.\n");

		return 1;
	}

	if (link_opt->bSetImageBase)
	{
		PE_hdr.Optional_hdr.ImageBase = link_opt->ImageBase;
	}
	if (link_opt->bSetSectionAlign)
	{
		PE_hdr.Optional_hdr.SectionAlignment = link_opt->SectionAlign;
	}
	if (link_opt->bSetFileAlign)
	{
		PE_hdr.Optional_hdr.FileAlignment = link_opt->FileAlign;
	}
	if (link_opt->bSetSubsystem)
	{
		PE_hdr.Optional_hdr.Subsystem = (WORD)(link_opt->Subsystem);
	}
	if (link_opt->bSetLinkerVersion)
	{
		PE_hdr.Optional_hdr.MajorLinkerVersion = (BYTE)(link_opt->LinkerVersion[0]);
		PE_hdr.Optional_hdr.MinorLinkerVersion = (BYTE)(link_opt->LinkerVersion[1]);
	}
	if (link_opt->bSetOSVersion)
	{
		PE_hdr.Optional_hdr.MajorOperatingSystemVersion = link_opt->OSVersion[0];
		PE_hdr.Optional_hdr.MinorOperatingSystemVersion = link_opt->OSVersion[1];
	}
	if (link_opt->bSetImageVersion)
	{
		PE_hdr.Optional_hdr.MajorImageVersion = link_opt->ImageVersion[0];
		PE_hdr.Optional_hdr.MinorImageVersion = link_opt->ImageVersion[1];
	}
	if (link_opt->bSetSubSysVersion)
	{
		PE_hdr.Optional_hdr.MajorSubsystemVersion = link_opt->SubSysVersion[0];
		PE_hdr.Optional_hdr.MinorSubsystemVersion = link_opt->SubSysVersion[1];
	}
	if (link_opt->bSetStackSize)
	{
		PE_hdr.Optional_hdr.SizeOfStackReserve = link_opt->StackSize;
	}
	if (link_opt->bSetStackCommitSize)
	{
		PE_hdr.Optional_hdr.SizeOfStackCommit = link_opt->StackCommitSize;
	}
	if (link_opt->bSetHeapSize)
	{
		PE_hdr.Optional_hdr.SizeOfHeapReserve = link_opt->HeapSize;
	}
	if (link_opt->bSetHeapCommitSize)
	{
		PE_hdr.Optional_hdr.SizeOfHeapCommit = link_opt->HeapCommitSize;
	}

	return 0;
}

static int ResolveSymbols(Link_Data * const link_data)
{
	int bUnresolved = 0;
	DWORD i;
	DWORD j;
	DWORD m;
	DWORD n;

	for (i = 0; i < link_data->NumberOfObjects; ++i)
	{
		for (j = 0; j < link_data->objects[i].NumberOfSymbols; ++j)
		{
			if ((link_data->objects[i].symbols[j].Name) && (!link_data->objects[i].symbols[j].PointerToSection) && (IMAGE_SYM_UNDEFINED == link_data->objects[i].symbols[j].SectionNumber))
			{
				for (m = 0; m < link_data->NumberOfObjects; ++m)
				{
					for (n = 0; n < link_data->objects[m].NumberOfSymbols; ++n)
					{
						if ((link_data->objects[m].symbols[n].Name) && (link_data->objects[m].symbols[n].PointerToSection) && (IMAGE_SYM_CLASS_EXTERNAL == link_data->objects[m].symbols[n].StorageClass) && (0 == sstrcmp(link_data->objects[i].symbols[j].Name, link_data->objects[m].symbols[n].Name)))
						{
							link_data->objects[i].symbols[j].PointerToSection = link_data->objects[m].symbols[n].PointerToSection;
							link_data->objects[i].symbols[j].Offset = link_data->objects[m].symbols[n].Offset;
							goto next;
						}
					}
				}
				for (m = 0; m < link_data->NumberOfLibraries; ++m)
				{
					DWORD begin = 0;
					DWORD end = link_data->libraries[m].NumberOfSymbols;
					DWORD distance = end - begin;
					DWORD half = begin + (distance >> 1);

					while (distance)
					{
						int result = sstrcmp(link_data->objects[i].symbols[j].Name, link_data->libraries[m].SymbolNames[half]);
						if (0 == result)
						{
							link_data->objects[i].symbols[j].LibraryIndex = m;
							link_data->objects[i].symbols[j].SymbolIndex = half;
							for (n = 0; n < link_data->libraries[m].NumberOfReferences; ++n)
							{
								result = sstrcmp(link_data->objects[i].symbols[j].Name, link_data->libraries[m].ReferenceNames[n]);
								if (0 == result)
								{
									break;
								}
								else if (result < 0)
								{
									DWORD k = link_data->libraries[m].NumberOfReferences;
									while (k > n)
									{
										link_data->libraries[m].ReferenceNames[k] = link_data->libraries[m].ReferenceNames[k - 1];
										--k;
									}
									link_data->libraries[m].ReferenceNames[n] = link_data->libraries[m].SymbolNames[half];
									++(link_data->libraries[m].NumberOfReferences);
									break;
								}
							}
							if (n == link_data->libraries[m].NumberOfReferences)
							{
								link_data->libraries[m].ReferenceNames[n] = link_data->libraries[m].SymbolNames[half];
								++(link_data->libraries[m].NumberOfReferences);
							}
							goto next;
						}
						else if (result > 0)
						{
							begin = half + 1;
						}
						else
						{
							end = half;
						}

						distance = end - begin;
						half = begin + (distance >> 1);
					}
				}

				bUnresolved = 1;
				PrintInfoAux("Unresolved External Symbol: ");
				PrintInfo(link_data->objects[i].symbols[j].Name);
				PrintInfoAux("\n");
			}
next:	;
		}
	}
	if (bUnresolved)
	{
		return ERR_UNRESOLVED_SYMBOL;
	}

	return 0;
}

static int HandleContent(Link_Data * const link_data)
{
	DWORD align_mask;
	DWORD i;
	DWORD j;
	DWORD k;

	for (i = 0; i < link_data->NumberOfObjects; ++i)
	{
		for (j = 0; j < link_data->objects[i].NumberOfSections; ++j)
		{
			switch (link_data->objects[i].sections[j].Characteristics & ALIGN_MASK)
			{
			case IMAGE_SCN_ALIGN_1BYTES:
				align_mask = 0;
				break;
			case IMAGE_SCN_ALIGN_2BYTES:
				align_mask = 1;
				break;
			case IMAGE_SCN_ALIGN_4BYTES:
				align_mask = 3;
				break;
			case IMAGE_SCN_ALIGN_8BYTES:
				align_mask = 7;
				break;
			case IMAGE_SCN_ALIGN_16BYTES:
				align_mask = 15;
				break;
			case IMAGE_SCN_ALIGN_32BYTES:
				align_mask = 31;
				break;
			case IMAGE_SCN_ALIGN_64BYTES:
				align_mask = 63;
				break;
			case IMAGE_SCN_ALIGN_128BYTES:
				align_mask = 127;
				break;
			case IMAGE_SCN_ALIGN_256BYTES:
				align_mask = 255;
				break;
			case IMAGE_SCN_ALIGN_512BYTES:
				align_mask = 511;
				break;
			case IMAGE_SCN_ALIGN_1024BYTES:
				align_mask = 1023;
				break;
			case IMAGE_SCN_ALIGN_2048BYTES:
				align_mask = 2047;
				break;
			case IMAGE_SCN_ALIGN_4096BYTES:
				align_mask = 4095;
				break;
			case IMAGE_SCN_ALIGN_8192BYTES:
				align_mask = 8191;
				break;
			default:
				align_mask = 15;
			}

			if (align_mask < 15)
			{
				align_mask = 15;
			}

			link_data->objects[i].sections[j].VirtualSize = ((link_data->objects[i].sections[j].SizeOfRawData + align_mask) & (~align_mask));

			for (k = 0; k < link_data->NumberOfSectionAux; ++k)
			{
				if (0 == sstrcmp(link_data->objects[i].sections[j].Name, link_data->sectionAux[k]->Name) || (link_data->objects[i].sections[j].Characteristics == link_data->sectionAux[k]->Characteristics))
				{
					break;
				}
			}
			if (k == link_data->NumberOfSectionAux)
			{
				++link_data->NumberOfSectionAux;
			}
			else
			{
				if (link_data->objects[i].sections[j].Characteristics != link_data->sectionAux[k]->Characteristics)
				{
					PrintInfoAux("Inconsistent Section Characteristics: ");
					PrintInfo(link_data->objects[i].sections[j].Name);
					PrintInfoAux("\nCurrent: 0x");
					PrintDWORD(link_data->objects[i].sections[j].Characteristics);
					PrintInfoAux("\nPrevious: 0x");
					PrintDWORD(link_data->sectionAux[k]->Characteristics);
					PrintInfoAux("\n");
				}

				link_data->objects[i].sections[j].VirtualAddress = link_data->sectionAux[k]->VirtualSize + link_data->sectionAux[k]->VirtualAddress;
			}

			link_data->objects[i].sections[j].Next = link_data->sectionAux[k];
			link_data->sectionAux[k] = link_data->objects[i].sections + j;
		}
	}

	return ResolveSymbols(link_data);
}

#define FindSection(SectionName, Section) \
do \
{ \
	for (i = 0; i < link_data->NumberOfSectionAux; ++i) \
	{ \
		if (0 == sstrcmp(link_data->sectionAux[i]->Name, (SectionName))) \
		{ \
			(Section) = link_data->sectionAux[i]; \
			break; \
		} \
	} \
} while(0)

static int GenerateOutput(const Link_Option * const link_opt, Link_Data * const link_data)
{
	char MapFileName[256] = "";
	char OutputFileName[256] = "a.exe";
	IMAGE_SECTION_HEADER CodeSectionHeader = { ".text", 0 };
	IMAGE_SECTION_HEADER DataSectionHeader = { ".data", 0 };
	Section *pSection[4] = {0};
	Section *CurrentSection = NULL;
	DWORD SectionOffset = 0;
	DWORD DataBaseAddress = 0;
	DWORD ImportDirectoryOffset = 0;
	DWORD SizeOfImportDirectoryTable = 0;
	DWORD SizeOfImportAddressTable = 0;
	DWORD SizeOfNameTable = 0;
	DWORD EntryPoint = 0;
	DWORD *ImportAddressOffset = NULL;
	DWORD SizeOfRawData_Code = 0;
	BYTE *PointerToRawData_Code = NULL;
	DWORD SizeOfRawData_Data = 0;
	BYTE *PointerToRawData_Data = NULL;
	HANDLE hFile;
	DWORD i;
	DWORD j;
	DWORD k;

	FindSection(".text", pSection[0]);
	FindSection(".rdata", pSection[1]);
	FindSection(".data", pSection[2]);
	FindSection(".bss", pSection[3]);

	ImportAddressOffset = (DWORD *)Malloc(link_data->NumberOfLibraries * sizeof(DWORD));

	for (i = 0; i < link_data->NumberOfLibraries; ++i)
	{
		if (link_data->libraries[i].NumberOfReferences)
		{
			ImportAddressOffset[i] = SizeOfImportAddressTable;

			SizeOfImportDirectoryTable += sizeof(IMAGE_IMPORT_DIRECTORY);
			SizeOfImportAddressTable += ((link_data->libraries[i].NumberOfReferences + 1) * sizeof(DWORD));
		}
	}

	SectionOffset = ((SizeOfImportAddressTable + 15) & (~15));

	if (pSection[0])
	{
		CurrentSection = pSection[0];
		while (CurrentSection)
		{
			CurrentSection->VirtualBaseAddress = CODE_BASE_ADDRESS;
			CurrentSection->VirtualAddress += SectionOffset;
			CurrentSection = CurrentSection->Next;
		}

		SectionOffset = pSection[0]->VirtualAddress + pSection[0]->VirtualSize;
	}

	if (pSection[1])
	{
		CurrentSection = pSection[1];
		while (CurrentSection)
		{
			CurrentSection->VirtualBaseAddress = CODE_BASE_ADDRESS;
			CurrentSection->VirtualAddress += SectionOffset;
			CurrentSection = CurrentSection->Next;
		}

		SectionOffset = pSection[1]->VirtualAddress + pSection[1]->VirtualSize;
	}

	if (SizeOfImportDirectoryTable)
	{
		IMAGE_IMPORT_DIRECTORY *ImportDirectory;

		SizeOfImportDirectoryTable += sizeof(IMAGE_IMPORT_DIRECTORY);

		ImportDirectoryOffset = SectionOffset;

		k = SectionOffset + SizeOfImportDirectoryTable + SizeOfImportAddressTable;

		PointerToRawData_Code = (BYTE *)Malloc(k);

		ImportDirectory = (IMAGE_IMPORT_DIRECTORY *)(PointerToRawData_Code + ImportDirectoryOffset);

		for (i = 0; i < link_data->NumberOfLibraries; ++i)
		{
			if (link_data->libraries[i].NumberOfReferences)
			{
				for (j = 0; j < link_data->libraries[i].NumberOfReferences; ++j)
				{
					DWORD len = 0;
					while (link_data->libraries[i].ReferenceNames[j][len])
					{
						if ('@' == link_data->libraries[i].ReferenceNames[j][len])
						{
							link_data->libraries[i].ReferenceNames[j][len] = '\0';
							break;
						}

						++len;
					}

					SectionOffset = SizeOfNameTable;
					SizeOfNameTable += ((lstrlen(link_data->libraries[i].ReferenceNames[j]) + 2 + 1 + 1 - (sizeof("__imp__") - 1)) & (~1));
					PointerToRawData_Code = (BYTE *)Realloc(PointerToRawData_Code, k + SizeOfNameTable);
					sstrcpy(PointerToRawData_Code + k + SectionOffset + 2, link_data->libraries[i].ReferenceNames[j] + (sizeof("__imp__") - 1));
					*((DWORD *)(PointerToRawData_Code + ImportAddressOffset[i]) + j) = k + SectionOffset + CODE_BASE_ADDRESS;
					*((DWORD *)(PointerToRawData_Code + ImportDirectoryOffset + SizeOfImportDirectoryTable + ImportAddressOffset[i]) + j) = k + SectionOffset + CODE_BASE_ADDRESS;
				}

				SectionOffset = SizeOfNameTable;
				SizeOfNameTable += ((lstrlen(link_data->libraries[i].LibraryName) + 1 + 1) & (~1));
				PointerToRawData_Code = (BYTE *)Realloc(PointerToRawData_Code, k + SizeOfNameTable);
				sstrcpy(PointerToRawData_Code + k + SectionOffset, link_data->libraries[i].LibraryName);

				ImportDirectory->ImportLookupTable = ImportDirectoryOffset + SizeOfImportDirectoryTable + ImportAddressOffset[i] + CODE_BASE_ADDRESS;
				ImportDirectory->TimeDateStamp = 0;
				ImportDirectory->ForwarderChain = 0;
				ImportDirectory->Name = k + SectionOffset + CODE_BASE_ADDRESS;
				ImportDirectory->ImportAddressTable = ImportAddressOffset[i] + CODE_BASE_ADDRESS;
				++ImportDirectory;
			}
		}

		SectionOffset = k + SizeOfNameTable;
	}

	DataBaseAddress = ((CODE_BASE_ADDRESS + SectionOffset + link_opt->SectionAlign - 1) & (~(link_opt->SectionAlign - 1)));
	SizeOfRawData_Code = ((SectionOffset + link_opt->FileAlign - 1) & (~(link_opt->FileAlign - 1)));
	PointerToRawData_Code = (BYTE *)Realloc(PointerToRawData_Code, SizeOfRawData_Code);

	SectionOffset = 0;

	if (pSection[2])
	{
		CurrentSection = pSection[2];
		while (CurrentSection)
		{
			CurrentSection->VirtualBaseAddress += DataBaseAddress;
			CurrentSection->VirtualAddress += SectionOffset;
			CurrentSection = CurrentSection->Next;
		}

		SectionOffset = pSection[2]->VirtualAddress + pSection[2]->VirtualSize;
	}

	if (pSection[3])
	{
		CurrentSection = pSection[3];
		while (CurrentSection)
		{
			CurrentSection->VirtualBaseAddress += DataBaseAddress;
			CurrentSection->VirtualAddress += SectionOffset;
			CurrentSection = CurrentSection->Next;
		}
	}

	SizeOfRawData_Data = ((SectionOffset + link_opt->FileAlign - 1) & (~(link_opt->FileAlign - 1)));
	PointerToRawData_Data = (BYTE *)Malloc(SizeOfRawData_Data);

	for (i = 0; i < link_data->NumberOfObjects; ++i)
	{
		for (j = 0; j < link_data->objects[i].NumberOfSymbols; ++j)
		{
			if ((link_data->objects[i].symbols[j].Name) && (link_data->objects[i].symbols[j].PointerToSection) && (IMAGE_SYM_CLASS_EXTERNAL == link_data->objects[i].symbols[j].StorageClass) && (!Memcmp(link_data->objects[i].symbols[j].Name, link_opt->EntryPoint.ptr, link_opt->EntryPoint.length)))
			{
				Section *p = (Section *)(link_data->objects[i].symbols[j].PointerToSection);
				EntryPoint = p->VirtualBaseAddress + p->VirtualAddress + link_data->objects[i].symbols[j].Offset;
				goto next;
			}
		}
	}

	PrintInfoAux("Cannot find entry point: ");
	PrintInfoEx(link_opt->EntryPoint.ptr, link_opt->EntryPoint.length);
	PrintInfoAux("\n");
next:
	if (link_opt->bSetMapFileName)
	{
		Memcpy(MapFileName, link_opt->MapFileName.ptr, link_opt->MapFileName.length);
		MapFileName[link_opt->MapFileName.length] = '\0';

		hFile = CreateFile(MapFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

		if (INVALID_HANDLE_VALUE == hFile)
		{
			PrintInfoAux("Cannot open file: ");
			PrintInfo(MapFileName);
			PrintInfoAux("\n");

			return 0;
		}

		for (i = 0; i < link_data->NumberOfObjects; ++i)
		{
			for (j = 0; j < link_data->objects[i].NumberOfSymbols; ++j)
			{
				if ((link_data->objects[i].symbols[j].Name) && (link_data->objects[i].symbols[j].PointerToSection) && ((IMAGE_SYM_CLASS_EXTERNAL == link_data->objects[i].symbols[j].StorageClass) || (IMAGE_SYM_CLASS_STATIC == link_data->objects[i].symbols[j].StorageClass)))
				{
					BYTE buffer[8];
					Section *pSection = (Section *)(link_data->objects[i].symbols[j].PointerToSection);
					DumpDWORD(buffer, link_opt->ImageBase + pSection->VirtualBaseAddress + pSection->VirtualAddress + link_data->objects[i].symbols[j].Offset);
					WriteFile(hFile, "0x", sizeof("0x") - 1, &k, NULL);
					WriteFile(hFile, buffer, 8, &k, NULL);
					WriteFile(hFile, ": ", sizeof(": ") - 1, &k, NULL);
					WriteFile(hFile, link_data->objects[i].symbols[j].Name, lstrlen(link_data->objects[i].symbols[j].Name), &k, NULL);
					WriteFile(hFile, "\n", sizeof("\n") - 1, &k, NULL);
				}
			}
		}

		CloseHandle(hFile);
	}

	for (i = 0; i < link_data->NumberOfObjects; ++i)
	{
		for (j = 0; j < link_data->objects[i].NumberOfSections; ++j)
		{
			if (link_data->objects[i].sections[j].PointerToRelocations && link_data->objects[i].sections[j].NumberOfRelocations)
			{
				for (k = 0; k < link_data->objects[i].sections[j].NumberOfRelocations; ++k)
				{
					Symbol *pSymbol = ((Symbol *)(link_data->objects[i].sections[j].PointerToRelocations[k].PointerToSymbolTable)) + (link_data->objects[i].sections[j].PointerToRelocations[k].SymbolTableIndex);
					Section *pSection = (Section *)(link_data->objects[i].sections[j].PointerToRelocations[k].PointerToSection);
					Section *pRelocateSection = (Section *)(pSymbol->PointerToSection);
					if (pRelocateSection)
					{
						if (IMAGE_REL_I386_DIR32 == link_data->objects[i].sections[j].PointerToRelocations[k].Type)
						{
							*((DWORD *)(pSection->PointerToRawData + link_data->objects[i].sections[j].PointerToRelocations[k].VirtualAddress)) += (link_opt->ImageBase + pRelocateSection->VirtualBaseAddress + pRelocateSection->VirtualAddress + pSymbol->Offset);
						}
						else if (IMAGE_REL_I386_REL32 == link_data->objects[i].sections[j].PointerToRelocations[k].Type)
						{
							*((DWORD *)(pSection->PointerToRawData + link_data->objects[i].sections[j].PointerToRelocations[k].VirtualAddress)) = pRelocateSection->VirtualBaseAddress + pRelocateSection->VirtualAddress + pSymbol->Offset - (pSection->VirtualBaseAddress + pSection->VirtualAddress + link_data->objects[i].sections[j].PointerToRelocations[k].VirtualAddress + 4);
						}
						else
						{
							DumpDWORD(PrintableDWORD, link_data->objects[i].sections[j].PointerToRelocations[k].Type);
							PrintInfoAux("Unsupported Relocation Type: 0x");
							PrintInfoAux(PrintableDWORD);
						}
					}
					else
					{
						DWORD ReferenceIndex;
						for (ReferenceIndex = 0; ReferenceIndex < link_data->libraries[pSymbol->LibraryIndex].NumberOfReferences; ++ReferenceIndex)
						{
							if (0 == sstrcmp(link_data->libraries[pSymbol->LibraryIndex].SymbolNames[pSymbol->SymbolIndex], link_data->libraries[pSymbol->LibraryIndex].ReferenceNames[ReferenceIndex]))
							{
								break;
							}
						}
						if (IMAGE_REL_I386_DIR32 == link_data->objects[i].sections[j].PointerToRelocations[k].Type)
						{
							*((DWORD *)(pSection->PointerToRawData + link_data->objects[i].sections[j].PointerToRelocations[k].VirtualAddress)) += (link_opt->ImageBase + CODE_BASE_ADDRESS + ImportAddressOffset[pSymbol->LibraryIndex] + ReferenceIndex * sizeof(DWORD));
						}
						else
						{
							DumpDWORD(PrintableDWORD, link_data->objects[i].sections[j].PointerToRelocations[k].Type);
							PrintInfoAux("Unsupported Relocation Type: 0x");
							PrintInfoAux(PrintableDWORD);
						}
					}
				}
			}
		}
	}

	if (pSection[0])
	{
		CurrentSection = pSection[0];

		while (CurrentSection)
		{
			Memcpy(PointerToRawData_Code + CurrentSection->VirtualAddress, CurrentSection->PointerToRawData, CurrentSection->SizeOfRawData);
			Memset(PointerToRawData_Code + CurrentSection->VirtualAddress + CurrentSection->SizeOfRawData, CODE_PADDING, CurrentSection->VirtualSize - CurrentSection->SizeOfRawData);
			CurrentSection = CurrentSection->Next;
		}

		CurrentSection = pSection[0];
	}

	if (pSection[1])
	{
		CurrentSection = pSection[1];

		while (CurrentSection)
		{
			Memcpy(PointerToRawData_Code + CurrentSection->VirtualAddress, CurrentSection->PointerToRawData, CurrentSection->SizeOfRawData);
			CurrentSection = CurrentSection->Next;
		}

		CurrentSection = pSection[1];
	}

	CodeSectionHeader.Misc.VirtualSize = CurrentSection->VirtualAddress + CurrentSection->VirtualSize + SizeOfImportDirectoryTable + SizeOfImportAddressTable + SizeOfNameTable;
	CodeSectionHeader.VirtualAddress = CurrentSection->VirtualBaseAddress;
	CodeSectionHeader.SizeOfRawData = SizeOfRawData_Code;
	CodeSectionHeader.PointerToRelocations = 0;
	CodeSectionHeader.PointerToLinenumbers = 0;
	CodeSectionHeader.NumberOfRelocations = 0;
	CodeSectionHeader.NumberOfLinenumbers = 0;
	CodeSectionHeader.Characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;

	if (pSection[2])
	{
		CurrentSection = pSection[2];

		DataSectionHeader.Misc.VirtualSize = CurrentSection->VirtualAddress + CurrentSection->VirtualSize;
		DataSectionHeader.VirtualAddress = CurrentSection->VirtualBaseAddress;
		DataSectionHeader.SizeOfRawData = SizeOfRawData_Data;

		while (CurrentSection)
		{
			Memcpy(PointerToRawData_Data + CurrentSection->VirtualAddress, CurrentSection->PointerToRawData, CurrentSection->SizeOfRawData);
			CurrentSection = CurrentSection->Next;
		}
	}

	if (pSection[3])
	{
		CurrentSection = pSection[3];

		DataSectionHeader.Misc.VirtualSize += (CurrentSection->VirtualAddress + CurrentSection->VirtualSize);
		DataSectionHeader.VirtualAddress = CurrentSection->VirtualBaseAddress;
	}

	DataSectionHeader.PointerToRelocations = 0;
	DataSectionHeader.PointerToLinenumbers = 0;
	DataSectionHeader.NumberOfRelocations = 0;
	DataSectionHeader.NumberOfLinenumbers = 0;
	DataSectionHeader.Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;

	if (pSection[0] || pSection[1])
	{
		++PE_hdr.File_hdr.NumberOfSections;
		PE_hdr.Optional_hdr.SizeOfCode = ((CodeSectionHeader.Misc.VirtualSize + link_opt->FileAlign - 1) & (~(link_opt->FileAlign - 1)));;
		PE_hdr.Optional_hdr.BaseOfCode = CodeSectionHeader.VirtualAddress;
		PE_hdr.Optional_hdr.SizeOfImage = ((PE_hdr.Optional_hdr.BaseOfCode + PE_hdr.Optional_hdr.SizeOfCode + link_opt->SectionAlign - 1) & (~(link_opt->SectionAlign - 1)));
	}

	if (pSection[2] || pSection[3])
	{
		++PE_hdr.File_hdr.NumberOfSections;
		PE_hdr.Optional_hdr.SizeOfInitializedData = ((DataSectionHeader.Misc.VirtualSize + link_opt->FileAlign - 1) & (~(link_opt->FileAlign - 1)));
		PE_hdr.Optional_hdr.BaseOfData = DataSectionHeader.VirtualAddress;
		PE_hdr.Optional_hdr.SizeOfImage = ((PE_hdr.Optional_hdr.BaseOfData + PE_hdr.Optional_hdr.SizeOfInitializedData + link_opt->SectionAlign - 1) & (~(link_opt->SectionAlign - 1)));
	}

	PE_hdr.Optional_hdr.AddressOfEntryPoint = EntryPoint;
	PE_hdr.Optional_hdr.SizeOfHeaders = ((sizeof(PE_hdr) + PE_hdr.File_hdr.NumberOfSections * IMAGE_SIZEOF_SECTION_HEADER + link_opt->FileAlign - 1) & (~(link_opt->FileAlign - 1)));
	PE_hdr.Optional_hdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (0 == SizeOfImportDirectoryTable ? 0 : CODE_BASE_ADDRESS + ImportDirectoryOffset);
	PE_hdr.Optional_hdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = SizeOfImportDirectoryTable;
	PE_hdr.Optional_hdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress = (0 == SizeOfImportAddressTable ? 0 : CODE_BASE_ADDRESS);
	PE_hdr.Optional_hdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size = SizeOfImportAddressTable;

	CodeSectionHeader.PointerToRawData = PE_hdr.Optional_hdr.SizeOfHeaders;
	DataSectionHeader.PointerToRawData = SizeOfRawData_Data ? CodeSectionHeader.PointerToRawData + CodeSectionHeader.SizeOfRawData : 0;

	if (link_opt->bSetOutputFileName)
	{
		Memcpy(OutputFileName, link_opt->OutputFileName.ptr, link_opt->OutputFileName.length);
		OutputFileName[link_opt->OutputFileName.length] = '\0';
	}

	hFile = CreateFile(OutputFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (INVALID_HANDLE_VALUE == hFile)
	{
		PrintInfoAux("Cannot open file: ");
		PrintInfo(OutputFileName);
		PrintInfoAux("\n");

		return 0;
	}

	WriteFile(hFile, &PE_hdr, sizeof(PE_hdr), &i, NULL);

	if (pSection[0] || pSection[1])
	{
		WriteFile(hFile, &CodeSectionHeader, IMAGE_SIZEOF_SECTION_HEADER, &i, NULL);
	}

	if (pSection[2] || pSection[3])
	{
		WriteFile(hFile, &DataSectionHeader, IMAGE_SIZEOF_SECTION_HEADER, &i, NULL);
	}

	k = PE_hdr.Optional_hdr.SizeOfHeaders - (sizeof(PE_hdr) + PE_hdr.File_hdr.NumberOfSections * IMAGE_SIZEOF_SECTION_HEADER);
	WriteFile(hFile, (BYTE *)Malloc(k), k, &i, NULL);

	if (PointerToRawData_Code)
	{
		WriteFile(hFile, PointerToRawData_Code, SizeOfRawData_Code, &i, NULL);
	}

	if (PointerToRawData_Data)
	{
		WriteFile(hFile, PointerToRawData_Data, SizeOfRawData_Data, &i, NULL);
	}

	CloseHandle(hFile);

	return 0;
}

void start(void)
{
	int ret;

	if (ret = ParseCommandLine(GetCommandLine(), &cmd))
	{
		return;
	}
	if (ret = ProcessCommand(&cmd, &opt))
	{
		return;
	}
	if (ret = HandleOptions(&opt))
	{
		return;
	}
	if (ret = LoadFiles(&cmd, &data))
	{
		return;
	}
	if (ret = HandleContent(&data))
	{
		return;
	}
	if (ret = GenerateOutput(&opt, &data))
	{
		return;
	}
}
